﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Text.RegularExpressions;
using NGourd.Core.Steps;
using NGourd.Core.Reports;
using NGourd.Core.Features;
using NGourd.Core.Exceptions;

namespace NGourd.Core.Runners
{

    class StepLibrary
    {
        private IList<LifecycleAwareSteps> Steps = new List<LifecycleAwareSteps>();
        private IList<ExecutableStep> ExecutableSteps = new List<ExecutableStep>();

        public void AddSteps(object obj)
        {
            Steps.Add(new LifecycleAwareSteps(obj));
            AddExecutableSteps(obj);
        }

        private void AddExecutableSteps(object obj)
        {
            foreach (var method in obj.GetType().GetMethods())
            {
                var stepsAttr = method.GetCustomAttributes(typeof(StepAttribute), true);
                foreach (StepAttribute att in stepsAttr)
                    ExecutableSteps.Add(new ExecutableStep(att.Regex, obj, method));
            }
        }

        public ExecutableStep GetStepForText(string text)
        {

            var potentialSteps = ExecutableSteps.Where(s => s.CanExecute(text)).ToArray();
            if (potentialSteps.Length == 1)
                return potentialSteps[0];

            if (potentialSteps.Length == 0)
                return null;
            // potentialSteps.Length > 1
            throw new AmbiguousStepException(text, potentialSteps);
        }

        public void StartRun()
        {
            foreach (var step in Steps)
                step.BeforeAll();
        }

        public void StartScenario()
        {
            foreach (var step in Steps)
                step.Before();
        }

        public void EndScenario()
        {
            foreach (var step in Steps.Reverse())
                step.After();
        }

        public void EndRun()
        {
            foreach (var step in Steps.Reverse())
                step.AfterAll();
        }
    }

    class ExecutableStep
    {
        private readonly Regex Regex;
        private readonly object Target;
        private readonly MethodInfo Method;

        public ExecutableStep(Regex regex, object target, MethodInfo method)
        {
            Regex = regex;
            Target = target;
            Method = method;

            VerifyState();
        }

        private void VerifyState()
        {
            var numParams = Method.GetParameters().Count();

            var numRegexGroups = Regex.GetGroupNumbers().Count() - 1;
            if (numRegexGroups != numParams)
            {
                throw new InvalidOperationException(
                    String.Format(
                    "Number of groups in the step regular expression do not match the number of parameters in the method signature: {0}#{1}",
                    Target.GetType().FullName,
                    Method.ToString()
                    ));
            }
        }

        public bool CanExecute(string text)
        {
            return Regex.IsMatch(text);
        }

        public void Execute(string text)
        {
            var match = Regex.Match(text);
            if (match.Success == false)
                throw new ArgumentException("text must be matched by regular expression", "text");

            var parameters = match.Groups.Cast<Group>()
                                    .Skip(1) // The first element is the whole matched string
                                    .Select(g => g.Value)
                                    .ToArray();

            try
            {
                Method.Invoke(Target, parameters);
            }
            catch (TargetInvocationException tex)
            {
                throw AdjustedRealException(tex);
            }
        }

        private Exception AdjustedRealException(TargetInvocationException tex)
        {
            var remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
            var newStackTrace = String.Join(
                Environment.NewLine,
                tex.InnerException
                    .StackTrace.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
                        .SkipWhile(tr => tr.Contains("NGourd.Core."))
                        .TakeWhile(tr => tr.Contains("Ngourd.Core.") == false)
                    .ToArray()) + Environment.NewLine;

            remoteStackTraceString.SetValue(tex.InnerException, newStackTrace);

            return tex.InnerException;
        }

        public override string ToString()
        {
            return String.Format("{0}/{1}/{0}{2}.{3}", Environment.NewLine, Regex.ToString(), Target.GetType().FullName, Method.Name);
        }
    }


    class LifecycleAwareSteps
    {
        public readonly object Steps;
        private readonly Type type;
        public readonly Action BeforeAll = () => { };
        public readonly Action Before = () => { };
        public readonly Action After = () => { };
        public readonly Action AfterAll = () => { };
        public LifecycleAwareSteps(object obj)
        {
            if (obj == null)
                throw new ArgumentNullException("obj");

            Steps = obj;
            type = Steps.GetType();

            BeforeAll = GetActionCalled("BeforeAll");
            Before = GetActionCalled("Before");
            After = GetActionCalled("After");
            AfterAll = GetActionCalled("AfterAll");
        }

        public Action GetActionCalled(string name)
        {
            var method = type.GetMethod(name, new Type[] { });
            if (method != null)
                return Delegate.CreateDelegate(typeof(Action), Steps, method) as Action;
            return () => { };
        }
    }
}
